#!/usr/bin/env python
# -*- coding: utf-8 -*-

import subprocess, shutil

from smvn.config import *
from smvn.util import *
from smvn.artifact import Artifact
from smvn.pom import Pom,XmlPom

class Repo:
    localrepo = default_dir
    def __init__(self, url=default_repo):
        surl = url.strip()
        if surl[-1] == '/':
            self.url = surl[0:-1]
        else:
            self.url = surl

    def search_dir(self, dirname, pathlist, ver=default_ver):
        #ret = self.svn_list(dirname).strip()
        ret = pathlist.strip()
        if (ret != ""):
            dirs = ret.split()
            #for d in dirs: print(d)
            if (ver==default_ver):
                return dirname, dirs[-1]
            else:
                nver = ver.strip()+'/'
                if nver in ret:
                    return dirname, nver
        return dirname, ""

    def search_svn(self, group, name, ver=default_ver):
        dirname = pjoin(make_grouppath(group), name)
        if name == "":
            return dirname, ""
        return self.search_dir(dirname, self.svn_list(dirname), ver)

    def search_local(self, group, name, ver=default_ver):
        dirname = pjoin(make_grouppath(group), name)
        return self.search_dir(dirname, self.local_list(dirname), ver)

    def local_list(self, dirpath, filterdir=False):
        try:
            fullpath = pjoin(self.localrepo, dirpath)
            rlist = os.listdir(fullpath)
            logging.debug("local list %s"%(fullpath))
            ret = ""
            for r in rlist:
                if os.path.isdir(fullpath+'/'+r):
                    if not filterdir:
                        ret += r
                        ret += '/\n'
                else:
                    ret += r
                    ret += '\n'
            return ret
        except Exception, e:
            print("Error list", e)
            raise
            return ""

    def check_file(self, filename, artifact, shortname=""):
        head, tail = os.path.split(filename)
        if shortname != "":
            tail = shortname

        dstname = artifact.get_remote_filename(tail)
        fulldstname = pjoin(self.localrepo, dstname)
        md5name = fulldstname+'.md5'
        if not os.path.exists(md5name):
            print("A", filename)
            write_md5(fulldstname)
        else:
            cur = cal_md5(fulldstname)
            old = get_md5str(md5name)
            if (cur != old):
                print("M", filename)
            else:
                print("U", filename)

    def check_upmd5_copy(self, filename, fulldstname):
        # check md5 checksum
        ret = ""
        md5name = fulldstname+'.md5'
        if not os.path.exists(fulldstname):
            logging.info("Installing %s as %s", filename, fulldstname)
            ret = shutil.copy2(filename, fulldstname)
            write_md5(fulldstname)
        else:
            curstat = os.stat(filename)
            oldstat = os.stat(fulldstname)
            if (curstat.st_size == oldstat.st_size
                and int(curstat.st_mtime*1000) == int(oldstat.st_mtime*1000)
               ):
                logging.debug("Not installing %s as %s, all the same", filename, fulldstname)
            else:
                cur = cal_md5(fulldstname)
                old = get_md5str(md5name)
                # -- if mtime not equals, but md5 equals, 
                #    the dst file is touch by some other files
                #    we should overwrite it now
                ret = shutil.copy2(filename, fulldstname)
                if (cur != old):
                    logging.info("Installing %s as %s", filename, fulldstname)
                    write_md5(fulldstname)
                else:
                    logging.debug("Not installing %s as %s, the same", filename, fulldstname)
        return ret

    def install_file(self, filename, artifact, shortname=""):
        try:
            head, tail = os.path.split(filename)
            if shortname != "":
                tail = shortname

            dstname = artifact.get_remote_filename(tail)
            fulldstname = pjoin(self.localrepo, dstname)

            dstdir, tail = os.path.split(fulldstname)
            if not os.path.exists(dstdir): os.makedirs(dstdir)

            ret = self.check_upmd5_copy(filename, fulldstname)

            return ret
        except Exception, e:
            print("Error install", e)
            raise
            return ""

    def rename_dir2long(self, dstdir, artifact):
        """
        rename dir to long-format, dstdir should be a local dir
        """
        rlist = os.listdir(dstdir)
        for item in rlist:
            fullitem = pjoin(dstdir, item)
            #print(item, fullitem)
            if not os.path.isdir(fullitem):
                nfullitem = pjoin(dstdir, artifact.get_long_filename(item))
                if (item[0]!='.' and fullitem != nfullitem):
                    # 判断是否隐藏文件，并判断是否不等, 此时应采用item进行判断，而不用fullitem
                    logging.info("Renaming %s to %s", fullitem, nfullitem)
                    os.rename(fullitem, nfullitem)
            else:
                self.rename_dir2long(dstdir+'/'+item, artifact)

    def rename_dir2short(self, dstdir, artifact):
        rlist = os.listdir(dstdir)
        for item in rlist:
            fullitem = pjoin(dstdir, item)
            if not os.path.isdir(fullitem):
                nfullitem = pjoin(dstdir, artifact.get_short_filename(item))
                print(fullitem, nfullitem)
                if (item[0]!='.' and fullitem != nfullitem):
                    # 判断是否隐藏文件，并判断是否不等, 此时应采用item进行判断，而不用fullitem
                    logging.info("rename %s", nfullitem)
                    os.rename(fullitem, nfullitem)
            else:
                self.rename_dir2short(dstdir+'/'+item, artifact)

    def install_dir(self, dirname, artifact, shortname):
        try:
            dstdir = pjoin(self.localrepo, artifact.dirpath, shortname)

            logging.info("install dir %s as %s", dirname, dstdir)
            ret = shutil.copytree(dirname, dstdir);
            self.rename_dir2long(dstdir, artifact)
            return ret
        except Exception, e:
            print("Error install dir", e)
            raise
            return ""

    def local_resolve_pom(self, ori_p, depth=0):
        dmap = {}
        if depth == 0:
            dmap[make_grouppath(ori_p.groupId)+'/'+ori_p.artifactId] = ori_p.version
            logging.info('local resolve pom for %d %s'%(depth, dmap))
        #print(dmap)
        for p in ori_p.dependencies:
            dirname , ver = self.search_local(p.groupId, p.artifactId, p.version)
            
            if ver != "":
                ver = ver[0:-1]
                merge_map(dmap, dirname, ver)
                ndirname = dirname+'/'+ver
                # 下载依赖的pom
                if not os.path.exists(pjoin(self.localrepo, ndirname)):
                    self.svn_download(ndirname)
                # 解析依赖的pom
                sub_p = XmlPom(self.localrepo+'/'+ndirname+'/'+default_pom)
                sub_dmap = self.local_resolve_pom(sub_p, depth+1)
                # 合并dmap
                merge_maps(dmap, sub_dmap)
            else:
                logging.error("Local Repo %s %s not found!", p.artifactId, p.version)
        return dmap

    def svn_resolve_pom(self, ori_p, depth=0):
        """
         使用了递归
        """
        dmap = {}
        if depth == 0:
            dmap[make_grouppath(ori_p.groupId)+'/'+ori_p.artifactId] = ori_p.version
        #print(dmap)
        for p in ori_p.dependencies:
            dirname , ver = self.search_svn(p.groupId, p.artifactId, p.version)
            
            if ver != "":
                ver = ver[0:-1]
                merge_map(dmap, dirname, ver)
                ndirname = dirname+'/'+ver
                # 下载依赖的pom
                self.svn_download(ndirname)
                # 解析依赖的pom
                sub_p = XmlPom(self.localrepo+'/'+ndirname+'/'+default_pom)
                sub_dmap = self.svn_resolve_pom(sub_p, depth+1)
                # 合并dmap
                merge_maps(dmap, sub_dmap)
            else:
                logging.error("Repo %s %s not found!", p.artifactId, p.version)
        return dmap

    def local_svn_resolve(self, groupId, artifactId, version, depth=0):
        dirname, ver = self.search_local(groupId, artifactId, version)
        if ver != "":
            ndirname = dirname+'/'+ver[0:-1]
            # 解析依赖的pom
            p = XmlPom(self.localrepo+'/'+ndirname+'/'+default_pom)
            return self.local_resolve_pom(p, depth)
        else:
            dirname, ver = self.search_svn(groupId, artifactId, version)
            if ver != "":
                ndirname = dirname+'/'+ver[0:-1]
                # 下载依赖的pom
                self.svn_download(ndirname)
                # 解析依赖的pom
                p = XmlPom(self.localrepo+'/'+ndirname+'/'+default_pom)
                return self.local_resolve_pom(p, depth)
            else:
                sys.exit(-1)

    def local_resolve(self, groupId, artifactId, version, depth=0):
        dirname, ver = self.search_local(groupId, artifactId, version)
        if  ver != "":
            ndirname = dirname+'/'+ver[0:-1]
            # 解析依赖的pom
            p = XmlPom(self.localrepo+'/'+ndirname+'/'+default_pom)
            return self.local_resolve_pom(p, depth)
        else:
            logging.error("Artifact %s.%s not found!", groupId, artifactId)
            sys.exit(-1)

    def svn_resolve(self, groupId, artifactId, version):
        dirname, ver = self.search_svn(groupId, artifactId, version)
        if ver != "":
            ndirname = dirname+'/'+ver[0:-1]
            # 下载依赖的pom
            self.svn_download(ndirname)
            # 解析依赖的pom
            p = XmlPom(self.localrepo+'/'+ndirname+'/'+default_pom)
            return self.svn_resolve_pom(p)
        else:
            logging.error("Artifact %s.%s not found!", groupId, artifactId)
            sys.exit(-1)

    def svn_commit(self, arti):
        try:
            ret = subprocess.check_output(['svn', 'commit', pjoin(self.localrepo, arti.get_dir()),
                                           '-m"update %s.%s v%s"'%(arti.groupId, arti.name, arti.version)])
            print(ret)
        except subprocess.CalledProcessError, e:
            print(e)

    def svn_add(self, localpath, svnstr):
        """
         在增加文件内容时，需要临时切换目录
        """
        backpath = os.getcwd()
        os.chdir(localpath)
        for line in svnstr.split('\n'):
            pline = line.strip()
            if (len(pline) > 0 and pline[0] == '?'):
                filename = os.path.relpath(pline[1:].strip(), localpath)
                #print("get", filename)
                try:
                    ret = subprocess.check_output(['svn', 'add', filename])
                except subprocess.CalledProcessError, e:
                    print(e)
        os.chdir(backpath)

    def svn_check(self, artifact):
        localpath = pjoin(self.localrepo, artifact.get_dir())
        rlist = os.listdir(localpath)
        if ('.svn' not in rlist):
            self.svn_checkout(artifact.get_dir(),localpath)
        self.svn_update(localpath)
        try:
            ret = subprocess.check_output(['svn', 'status', localpath])
            print(ret)
            """
[url=]  L    abc.c               # svn已经在.svn目录锁定了abc.c
M      bar.c               # bar.c的内容已经在本地修改过了
X      3rd_party           # 这个目录是外部定义的一部分
?      foo.o               # svn并没有管理foo.o
!      some_dir            # svn管理这个，但它可能丢失或者不完整
~      qux                 # 作为file/dir/link进行了版本控制，但类型已经改变
I      .screenrc           # svn不管理这个，配置确定要忽略它
A  +   moved_dir           # 包含历史的添加，历史记录了它的来历
M  +   moved_dir/README    # 包含历史的添加，并有了本地修改
D      stuff/fish.c        # 这个文件预定要删除
A      stuff/loot/bloo.h   # 这个文件预定要添加
C      stuff/loot/lump.c   # 这个文件在更新时发生冲突
R      xyz.c               # 这个文件预定要被替换
    S  stuff/squawk        # 这个文件已经跳转到了分支"""
            self.svn_add(localpath, ret)
        except subprocess.CalledProcessError, e:
            os.chdir(backpath)
            print(e)

    def fetch_single(self, arti, pom, dst):
        """
         从本地repo获取文件内容
        """
        try:
            src = pjoin(self.localrepo, arti.dirpath, arti.version)
            rlist = os.listdir(src)
            #logging.info(" [fetch_single] Fetching of %s", rlist)
            ret = ""
            for item in rlist:
                #logging.info("[fetch_single item] Fetching %s/%s", src, item)
                if not os.path.isdir(src+'/'+item):
                    if item == "pom.xml":   # if it's a pom file, then copy it.
                        #name = os.path.split(arti.dirpath)[-1]
                        name = arti.dirpath.replace('/', '.')
                        logging.info("[pom] fetching %s.pom.xml", name)
                        if not os.path.exists(dst + '/pom' ):
                            os.makedirs(dst + '/pom')

                        shutil.copy(src+'/'+item, dst+'/pom/' + name + '.' + default_pom)
                        pass
                    else:                   # if it's not a pom file, then copy it.
                        fm, fe = os.path.splitext(item)
                        extra = ""
                        if fe == '.md5':
                            continue
                        elif fe == '.h':
                            extra = pom.get_header_prefix()
                        elif get_file_type(fe) == '.lib':
                            extra = pom.get_lib_prefix()

                        if extra != "":
                            if not os.path.exists(dst + '/' + extra):
                                os.makedirs(dst + '/' + extra)
                            extra += '/'

                        shortname = arti.get_short_filename(item)
                        reponame = pjoin(src, item)
                        localname = pjoin(dst, extra, shortname)
                        if (is_same_file(reponame, localname)):
                            logging.debug("[inner] not fetching %s, all the same", item)
                        else:
                            logging.info("[inner] fetcching %s as %s", item, shortname)
                            ret = shutil.copy2(reponame, localname)
                else:
                    # is a dir
                    logging.debug("skip dir %s", dst)
            #end of for

            # copy headdirs
            self.fetch_dir(pom.headerdirs, src, dst, arti, pom.get_header_prefix())
            self.fetch_dir(pom.resourcedirs, src, dst, arti, pom.get_resource_prefix())

            return ret
        except KeyError, e:
            print("Error fetch", e)
            return ""

    def fetch_dir(self, dirs, src, dst, arti, prefix=""):
        for k, v in dirs.items():
            if len(v)>1:
                fulldst = pjoin(dst, prefix, k)
                if not os.path.exists(fulldst):
                    os.makedirs(fulldst)
                for filename in v:
                    longname = arti.get_long_filename(filename)
                    #logging.info("[fetch_dir] fectching %s/%s as %s", k, longname, filename)
                    #ret = shutil.copy2(pjoin(src, k, longname), pjoin(fulldst, filename))

                    reponame = pjoin(src, k, longname)
                    localname = pjoin(fulldst, filename)
                    if (is_same_file(reponame, localname)):
                        logging.debug("[fetch_dir] not fetching %s/%s, same", k, longname)
                    else:
                        logging.info("[fetch_dir] fectching %s/%s as %s", k, longname, filename)
                        ret = shutil.copy2(reponame, localname)


    def fetch(self, dirpath, ver, dst):
        np = XmlPom(pjoin(self.localrepo, dirpath, ver, default_pom))
        a = Artifact(dirpath=dirpath, ver=ver)
        #print('Fetching np', np)
        self.fetch_single(a, np, dst)


    def svn_list(self, dirpath):
        try:
            logging.info("rlist " + self.url+'/'+dirpath)
            ret = subprocess.check_output(["svn", "list", self.url+'/'+dirpath])
            return ret
        except subprocess.CalledProcessError, e:
            print("Error list")
            return ""

    def svn_list_artifact(self, artifact):
        return self.svn_list(artifact.get_dir())

    def svn_import_file(self, filename, artifact):
        try:
            dstname = artifact.get_remote_filename(filename)
            print(dstname)
            ret = subprocess.check_output(["svn", "import", filename, pjoin(self.url, dstname),
                                           '-m"%s.%s,v%s, import %s"'%(artifact.groupId, artifact.name, artifact.version, filename)
                                          ])
            return ret
        except subprocess.CalledProcessError, e:
            print("Error import")
            return ""

    def svn_import_dir(self, dirname, dirpath, arti):
        try:
            print(dirname, self.url+'/'+dirpath)
            ret = subprocess.check_output(["svn", "import", dirname, pjoin(self.url, dirpath),
                                          '-m"%s.%s v%s from %s"'%(arti.groupId, arti.name, arti.version, dirname)
                                          ])
            return ret
        except subprocess.CalledProcessError, e:
            print("Error import dir", e)
            return -1

    def svn_checkout(self, dirpath, localpath, forceco=True):
        try:
            if (forceco):
                ret = subprocess.check_output(["svn", "checkout", "--force", self.url+'/'+dirpath, localpath])
            else:
                ret = subprocess.check_output(["svn", "checkout", self.url+'/'+dirpath, localpath])
            return ret
        except subprocess.CalledProcessError, e:
            print("Error svn_export", e)
            return -1

    def svn_export(self, dirpath, localpath):
        try:
            ret = subprocess.check_output(["svn", "export", self.url+'/'+dirpath, localpath])
            return ret
        except subprocess.CalledProcessError, e:
            print("Error svn_export", e)
            return -1

    def svn_update(self, dirpath):
        try:
            cwd = os.getcwd()
            os.chdir(dirpath)
            ret = subprocess.check_output(["svn", "update"])
            os.chdir(cwd)
            return ret
        except subprocess.CalledProcessError, e:
            os.chdir(cwd)
            print("Error svn_export", e)
            return -1

    def svn_download(self, dirname):
        dstpath = pjoin(self.localrepo, dirname)
        if not os.path.exists(dstpath):
            logging.info("Fresh checkout target %s", dirname)
            self.svn_checkout(dirname, dstpath)
        else:
            logging.info("Update target %s", dirname)
            self.svn_update(dstpath)


    def download_artifact(self, group, name, ver=default_ver):
        """
         download an artifact 
        """
        dirname, nver = self.search_svn(group, name, ver)
        if (nver != ""):
            self.svn_download(dirname+'/'+nver)

